# Pongathon
# Version 1.4.1
# Copyright 2014 Josef J. Polak III
# Pongathon is distributed under the terms of the GNU General Public License

# This file is part of Pongathon.

# Pongathon is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pongathon is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Game model for Pongathon.

import events
import views

class Game:
	"""Main model object. Views and controllers interact with this."""

	def __init__(self, evManager):
		self.evManager = evManager
		self.evManager.RegisterListener(self)

		self.state = 'intro'
		self.started = False


	def Start(self):
		self.state = 'playing'
		self.started = True
		
	def Notify(self, event):
		if isinstance(event, events.Tick):
			if self.started is True:
				self.p1.update()
				self.p2.update()
				self.ball.update()
		
		elif isinstance(event, events.GameStartRequest):
			if event.num == 1:
				self.p1 = Player(self.evManager, 1)
				self.p2 = AIPlayer(self.evManager, 2)
				self.p2.SetGame(self)
			elif event.num == 2:
				self.p1 = Player(self.evManager, 1)
				self.p2 = Player(self.evManager, 2)

			self.ball = Ball(self.evManager, (3.5, 3.5))
			self.ball.setPaddles(self.p1.paddle, self.p2.paddle)
			ev = events.SetStateRequest('playing')
			self.evManager.Post(ev)
		elif isinstance(event, events.SetStateRequest):
			if event.state == 'playing':
				if self.started is False:
					self.Start()
			else:
				self.started = False
				if event.state == 'intro':
					self.p1.score = 0
					self.p2.score = 0

		elif isinstance(event, events.BallCollision):
			if event.object == 'p1':
				self.ball.paddlebounce(self.p1.paddle.vector)
			elif event.object == 'p2':
				self.ball.paddlebounce(self.p2.paddle.vector)

		elif isinstance(event, events.PlayerScored):
			self.p1.paddle.center[1] = 302
			self.p1.paddle.vector = [0, 0]
			self.p1.paddle.state = 'stop'
			self.p2.paddle.center[1] = 302
			self.p2.paddle.vector = [0, 0]
			self.p2.paddle.state = 'stop'
			if event.num == 1:
				self.p1.score += 1
			elif event.num == 2:
				self.p2.score += 1

			if self.p1.score >= 10 or self.p2.score >= 10:
				self.evManager.Post(
						events.SetStateRequest('over'))

		elif isinstance(event, events.PaddleMoveRequest):
			if event.player == 1:
				self.p1.paddle.move(event.direction)
			elif event.player == 2:
				self.p2.paddle.move(event.direction)



class Player:
	"""Class representing human-controlled players."""
	def __init__(self, evManager, num):
		self.evManager = evManager
		self.game = None
		self.num = num
		self.score = 0

		self.paddle = Paddle(evManager, num)

	def __str__(self):
		return '<Player %s, %s>' % (self.name, id(self))

	def SetGame(self, game):
		self.game = game

	def update(self):
		self.paddle.update()


class AIPlayer(Player):
	"""My first attempt at a computer controlled opponent. It uses an
	inelegant, yet effective strategy."""

	def update(self):
		# Find the ball's position
		self.target = self.game.ball.center
		
		# Do I feel like exerting any effort?
		if self.target[0] >= 320:
		
			# Am I on target?
			if self.paddle.center[1] == self.target[1]:
				# I am on target, don't move!
				self.paddle.move('stop') 
			elif self.paddle.center[1] > self.target[1]:
				# Too far to the right
				self.paddle.move('up') 
			elif self.paddle.center[1] < self.target[1]:
				# Too far to the left
				self.paddle.move('down')
			self.paddle.update()


class Paddle:
	"""A class for player-controllable paddles. 'num' represents the player
	number that this paddle belongs to (icky, I know, but I'll come up with
	something better later."""

	def __init__(self, evManager, num):
		self.x_speed = 7
		self.vector = [0,0]
		self.state = 'stop'
		self.center = [12, 302]
		self.num = num

		# Initial positioning... not the best solution
		if self.num == 2:
			self.center[0] = 628

	def update(self):
		if self.center[1] > 169 and self.vector[1] < 0: 
			self.center[1] = self.center[1] + self.vector[1]
		elif self.center[1] < 435 and self.vector[1] > 0:
			self.center[1] = self.center[1] + self.vector[1]

	def move(self, direction):
		if direction == 'up':
			if self.center[1] > 169:
				self.vector = [0, -self.x_speed]
				self.state = 'moveup'
		elif direction == 'down':
			if self.center[1] < 435:
				self.vector = [0, self.x_speed]
				self.state = 'movedown'
		elif direction == 'stop':
			self.vector = [0, 0]
			
class Ball:
	"""A class for the ball. 'vector' is a two element tuple that contains
	the angle of the ball's motion in radians and its magnitude."""

	def __init__(self, evManager, vector):
		self.evManager = evManager
		self.vector = vector
		self.hit = False
		self.center = (320, 302)
		self.paddle1 = None
		self.paddle2 = None

	def setPaddles(self, paddle1, paddle2):
		self.paddle1 = paddle1;
		self.paddle2 = paddle2;

	def paddlebounce(self, paddlevector, corner=None):
		"""Computes the new ball vector after hitting a paddle.
		paddlevector is the velocity vector of the paddle that the
		ball has hit."""
		
		if not self.hit:
			(p_dx, p_dy) = paddlevector
			(b_dx, b_dy) = self.vector
			self.vector = [-b_dx, b_dy + 0.5*p_dy]
			self.hit = not self.hit

	def calcnewpos(self, center, vector):
		(dx, dy) = self.vector
		(x, y) = self.center
		(x, y) = (x + dx, y + dy)
		return (x, y)

	def update(self):
		newpos = self.calcnewpos(self.center, self.vector)
		self.center = newpos
		ev = None
		(x, y) = self.center
		(dx, dy) = self.vector
		
		if y - 12 <= 124:
			ev = events.BallCollision('wall')
			dy = -dy
			self.vector = (dx, dy)
		elif y + 12 >= 480:
			ev = events.BallCollision('wall')
			dy = -dy
			self.vector = (dx, dy)
		elif x - 12 <= 0:
			ev = events.PlayerScored(2)
			self.vector = (3.5, 3.5)
			self.center = (320, 302)
		elif x + 12 >= 640:
			ev = events.PlayerScored(1)
			self.vector = (-3.5, -3.5)
			self.center = (320, 302)
		elif x - 34 <= 0:
			dist = self.paddle1.center[1] - y
			if dist <= 45 and dist >= -45 and not self.hit:
				ev = events.BallCollision('p1')
		elif x + 34 >= 640:
			dist = self.paddle2.center[1] - y
			if dist <= 45 and dist >= -45 and not self.hit:
				ev = events.BallCollision('p2')
		else:
			if self.hit:
				self.hit = not self.hit

		if ev:
			self.evManager.Post(ev)
